package gameEngine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantLock;

import javax.media.opengl.GLCanvas;

import ui.userIO.UserInputInterpreter;
import ui.userIO.userInput.*;

public abstract class GameEngine implements Runnable, UserInputInterpreter
{
	/**
	 * the owner id assigned to this game engine for the purposes of determining
	 * the origin of user inputs
	 */
	byte owner;
	boolean networked;
	/**
	 * stores user input when not networked
	 * key=owner id, value=list of input by that user
	 */
	HashMap<Byte, HashMap<Class<? extends UserInput>, ArrayList<UserInput>>> ui = 
		new HashMap<Byte, HashMap<Class<? extends UserInput>, ArrayList<UserInput>>>();
	GLCanvas c;
	
	ReentrantLock lock = new ReentrantLock();;
	
	/**
	 * creates a new game engine
	 * @param networked true if the game engine is to communicate with a server over a network,
	 * false otherwise, if false then the owner id is defaultly set to 0 and an update thread is
	 * immediately started after initialization
	 * @param um
	 * @param c
	 */
	public GameEngine(boolean networked, GLCanvas c)
	{
		this.networked = networked;
		this.c = c;
		
		if(!networked)
		{
			owner = 0;
			new Thread(this).start();
		}
	}
	/**
	 * gets the owner of this game engine, all user inputs generated in this
	 * engine are attributed to this owner
	 * @return gets the owner of this game engine
	 */
	public byte getOwner()
	{
		return owner;
	}
	/**
	 * updates the game, only runs if game is not networked, clears
	 * the user input buffer every iteration
	 */
	public void run()
	{
		long diff;
		long start = System.currentTimeMillis();
		for(;;)
		{
			try
			{
				Thread.sleep(30);
			}
			catch(InterruptedException e){}
			diff = System.currentTimeMillis()-start;
			start = System.currentTimeMillis();
			
			HashMap<Byte, HashMap<Class<? extends UserInput>, ArrayList<UserInput>>> temp = 
				new HashMap<Byte, HashMap<Class<? extends UserInput>, ArrayList<UserInput>>>(ui);
			ui = new HashMap<Byte, HashMap<Class<? extends UserInput>, ArrayList<UserInput>>>();
			update(diff/1000.0, temp);
		}
	}
	private void update(double tdiff, HashMap<Byte, HashMap<Class<? extends UserInput>, ArrayList<UserInput>>> ui)
	{
		updateGame(tdiff, ui);
		c.repaint();
	}
	public abstract void updateGame(double tdiff, HashMap<Byte, HashMap<Class<? extends UserInput>, ArrayList<UserInput>>> ui);
	/**
	 * places user input in the hash map, this is called upon receiving
	 * input from the sever or (if non-networked) when the user generates
	 * input
	 * @param owner the byte id of the owning game engine that created the input
	 * @param userInput
	 */
	private synchronized void addUserInput(byte owner, UserInput userInput)
	{
		if(ui.get(owner) != null)
		{
			if(ui.containsKey(userInput.getClass()))
			{
				ui.get(owner).get(userInput.getClass()).add(userInput);
			}
			else
			{
				ArrayList<UserInput> temp = new ArrayList<UserInput>();
				temp.add(userInput);
				ui.get(owner).put(userInput.getClass(), temp);
			}
			//ui.get(owner).add(userInput);
		}
		else
		{
			ArrayList<UserInput> temp = new ArrayList<UserInput>();
			temp.add(userInput);
			//ui.put(owner, temp);
			ui.put(owner, new HashMap<Class<? extends UserInput>, ArrayList<UserInput>>());
			ui.get(owner).put(userInput.getClass(), temp);
		}
	}
	/**
	 * registers the user input with the appropriate authority, 
	 * if networked then the input is sent to the server, if
	 * non-networked then the input is registered with the engine
	 * 
	 * all input registered this way was generated by this client
	 * 
	 * @param input the input to be registered
	 */
	public void registerUserInput(UserInput input)
	{
		if(!networked)
		{
			addUserInput(owner, input);
		}
	}
}
